#include "SEL.h"
#include "SEL_painter.h"
#include "SEL_resources.h"
#include "cursor.h"

using namespace SEL;

#define MAP_RGBA8888(r, g, b, a) ((r<<24)|(g<<16)|(b<<8)|(a))

struct Character
{
    Surface *surface;
    char ch;
    int x;
    int y;
    int sx;
    int sy;    
    typedef std::vector <Character> Vector;
};

void 
print (Font *font, const std::string & text, Character::Vector & chars,
       int x, int y)
{
	if (!font)
		return;
	
	Sint16 dx = x;
	
	for (unsigned int i = 0; i < text.length (); i++)
	{
		if (text[i] == ' ')
		{
			dx += font->spaceWidth ();
			continue;
		}
		if (text[i] == '\n')
		{
			dx = x;
			y += font->height () + font->vSpacing ();
			continue;
		}

		FontCharacter *character = font->characterMap (text[i]);
		Character ch;
		ch.surface = character->convertedSurface ();
		ch.ch = text [i];
		ch.x = dx;
		ch.y = y;
		ch.sx = ch.sy = 0;
		chars.push_back (ch);
		
		dx += character->width () + font->hSpacing ();
	}
	
}

const unsigned int frame_duration = 25; // 25 ms = 40 fps

class App
{
public:
	Display *display;
	int bpp;
	int width;
	int height;
	int flags;

	Uta dirty;

	// current cursor
	Cursor  *cursor;
	Surface *cur_background; // surface for storing the background
				 // of the cursor (used during update)
	int cursor_old_x;
	int cursor_old_y;
	Rect cursor_old; // the old cursor position and size
	
	void setCursor (Cursor *cursor);
	void releaseCursor ();

	// redraw the cursor (if needed) and update dirty rectangles;
	void updateFrame ();
	void redrawBackground (const Rect & rect);

	// print the characters array to screen
	void chPrint (const Character::Vector & chars, int dx = 0, int dy = 0);
	
	// draw the background over the chars
	void chDelete (const Character::Vector & chars, int dx = 0, int dy = 0);
	
	// add the characters rectangles to dirty rects
	void chDirty (const Character::Vector & chars, int dx = 0, int dy = 0);
	
	void chRandomSpeed (Character::Vector & chars, int max);
	
	void chMove (Character::Vector & chars, int times = 1);

public: // resources
	struct
	{
		Font *avant18;
	} fonts;

	struct
	{
		Cursor *arrow1;
	} cursors;

	struct
	{
		Surface *logo1;
	} images;

	App ();
	~App ();

	void parseCommandLine (int argc, char **argv);

	void showMessage (const std::string & text, int speed = 5);

	int doWelcome ();
	
	int main (int argc, char **argv);
};

App::App ()
	: display (NULL), bpp (16), width (640), height (480), 
	  flags (SDL::SWSURFACE), cursor (NULL), cur_background (NULL),
	  cursor_old_x (0), cursor_old_y (0), cursor_old (Rect (0, 0, 0, 0))
{
}

App::~App ()
{
}

void
App::setCursor (Cursor *a_cursor)
{
	releaseCursor ();

	if (a_cursor)
	{
	    cursor = a_cursor;
	    cursor->ref ();

	    Uint32 flags = 0;
	    if (display->flags () & SDL::HWSURFACE == SDL::HWSURFACE)
		flags |= SDL::HWSURFACE;
	    cur_background = new Surface (flags, cursor->surface ()->w (),
					  cursor->surface ()->h (), * display->format ());
	}
}

void
App::releaseCursor ()
{
	if (cursor)
	    cursor->unref ();
	    
	if (cur_background)
	    cur_background->unref ();

	cursor = NULL;
	cur_background = NULL;
}

void
App::updateFrame ()
{
	bool update_cursor = false;

	display->resetClipRect ();

	if (cursor) // we have cursor
	{
		int x, y; // current position
		Mouse::mouseState (x, y);				

		Rect cur_rect;

		cur_rect = Rect (x - cursor->hotX (), y - cursor->hotY (),
				 cursor->surface ()->w (),
				 cursor->surface ()->h ());

		Rect clip_rect = Rect (0, 0, display->w (), display->h ());

		if ((x != cursor_old_x || y != cursor_old_y) || // moved cursor
	    	    (dirty.intersects (cur_rect))) 
		{
			update_cursor = true;

			// store the background
			cur_background->blit (display, & cur_rect, NULL);
			
			// blit the cursor
			display->blit (cursor->surface (), 
				       x - cursor->hotX (),
			    	       y - cursor->hotY ());

			dirty += Rect::intersect (cur_rect, clip_rect);

			// add old rectangle to clean old cursor
			if (cursor_old.w != 0)
			    dirty += Rect::intersect (cursor_old, clip_rect);
		}
		cursor_old_x = x;
		cursor_old_y = y;
		cursor_old = cur_rect;
	}
	
	SDL::Rect *rects; // dirty rectangles
	int num;

	num = dirty.getRectangles (rects);

	display->updateRects (num, rects);
	delete [] rects;
	dirty.clear ();
	
	if (update_cursor) // update the cursor background
	{
		display->blit (cur_background, 
			       cursor_old.x,
			       cursor_old.y);
	}
}

void
App::redrawBackground (const Rect & rect)
{
	Surface *logo = images.logo1;

	if (!logo)
		return;

	int y_count = (display->h () / logo->h ()) + 1;
	int x_count = (display->w () / logo->w ()) + 1;

	SDL::Rect old_clip = display->clipRect ();
	display->setClipRect (rect.x, rect.y, rect.w, rect.h);

	for (int y = 0; y < y_count; ++y)
	{
		for (int x = 0; x < x_count; ++x)
		{
			Rect r = Rect::intersect (rect, 
						  Rect (x * logo->w (),
						        y * logo->h (),
							logo->w (),
							logo->h ()));
			if (r.w != 0 && r.h != 0)
			{
				display->blit (logo, x * logo->w (),
					       y * logo->h ());
			}
		}
	}
	
	display->setClipRect (old_clip);
}

void
App::chPrint (const Character::Vector & chars, int dx, int dy)
{
	Character::Vector::const_iterator it = chars.begin ();
	while (it != chars.end ())
	{
		display->blit ((*it).surface, (*it).x + dx, (*it).y + dy);
		++it;
	}
}

void
App::chDelete (const Character::Vector & chars, int dx, int dy)
{
	Character::Vector::const_iterator it = chars.begin ();
	while (it != chars.end ())
	{
		redrawBackground (Rect ((*it).x + dx, (*it).y + dy, 
				  (*it).surface->w (), (*it).surface->h ()));
		++it;
	}    
}

void
App::chDirty (const Character::Vector & chars, int dx, int dy)
{
	Character::Vector::const_iterator it = chars.begin ();
	while (it != chars.end ())
	{

		Rect r = Rect ((*it).x + dx, (*it).y + dy, 
			       (*it).surface->w (), (*it).surface->h ());
			       
		dirty += Rect::intersect (r, display->clipRect ());

		++it;
	}    
}

void
App::chRandomSpeed (Character::Vector & chars, int max)
{
	Character::Vector::iterator it = chars.begin ();
	while (it != chars.end ())
	{
		int sx = (rand () % max);
		if (sx == 0 && max > 2)
			sx = 2;
		int sy = max - sx;
		(*it).sx = rand () % 2 ? sx : - sx;
		(*it).sy = rand () % 2 ? sy : - sy;
		
		++it;
	}
}

void
App::chMove (Character::Vector & chars, int times)
{
	Character::Vector::iterator it = chars.begin ();
	while (it != chars.end ())
	{
		(*it).x += (*it).sx * times;
		(*it).y += (*it).sy * times;
		++it;
	}
}

void 
App::parseCommandLine (int argc, char **argv)
{
	for (int i = 1; i < argc; ++i)
	{
		if (strcmp (argv [i], "-bpp") == 0 && i < argc - 1)
		{
			++i;
			bpp = atoi (argv [i]);
			if (bpp != 16 && bpp != 24 && bpp != 32)
			{
				std::cerr << "Invalid bpp specified!" << std::endl;
				exit (1);
			}
		}
		else if (strcmp (argv [i], "-fullscreen") == 0)
		{
			flags |= SDL::FULLSCREEN;
		}
		else if (strcmp (argv [i], "-hwsurface") == 0)
		{
			flags |= SDL::HWSURFACE;
		}
		else if (strcmp (argv [i], "-res") == 0 &&
			 i < argc - 2)
		{
			++i;
			width = atoi (argv [i]);
			++i;
			height = atoi (argv [i]);
		}
		else if (strcmp (argv [i], "-h") == 0 ||
			 strcmp (argv [i], "--help") == 0)
		{
			std::cout << "USAGE:" << std::endl;
			std::cout << argv [0] << " [-bpp (depth)] [-fullscreen] [-hwsurface] [-res (width) (height)]" << std::endl;
			exit (0);
		}
	}
}

void
App::showMessage (const std::string & text, int speed)
{
	redrawBackground (Rect (0, 0, display->w (), display->h ()));

	Character::Vector chars;

	Font *font = fonts.avant18;

	Sint16 text_width = font->computeWidth (text);
	Sint16 text_height = font->computeHeight (text);
	
	print (font, text, chars, 
	       (display->w () / 2) - (text_width / 2),
	       (display->h () / 2) - (text_height / 2));

	chRandomSpeed (chars, speed);

	int num_moves = display->w () / speed;

	chMove (chars, num_moves);

	dirty += Rect (0, 0, display->w (), display->h ());
	
	Uint32 time;
	int status = 0; // 0 - forward, 1 - backward, 2 quit
	int move = 0;

	while (status != 2)
	{
		time = SDL::GetTicks ();
		if (move < num_moves)
		{
			chDelete (chars);
			chDirty (chars);
			chMove (chars, status == 0 ? -1 : 1);
			chDirty (chars);
			chPrint (chars);
			move ++;
		} 
		else if (status == 1)
		{
			status = 2;
		}
		updateFrame ();
		int mx, my;
		if ((Mouse::mouseState (mx, my) != 0) && status != 1)
		{
			status = 1;
			if (move < num_moves)
				move = num_moves - move;
			else
				move = 0;
		}
		time = SDL::GetTicks () - time;
		if (time < frame_duration) {
			SDL::Delay (frame_duration - time);
		}
	}
	// clean screen
	chDelete (chars);
	chDirty (chars);
	updateFrame ();
}

int
App::doWelcome ()
{
	std::string text = 
		"Welcome!\n\n"
		"This is a demonstration of the \n"
		"Simple DirectMedia Layer Extension Library.\n"
		"This demo will show you some of the SEL features.\n\n"
		"Click to continue.";

	showMessage (text, 10);
	
	showMessage ("Sorry\nThe demo is unfinished :)\n", 20);
	
	return 1;
}

int 
App::main (int argc, char **argv)
{
    try 
    {
	if (argc > 1)
		parseCommandLine (argc, argv);

	init (INIT_VIDEO|INIT_EVENTTHREAD);

	resourcesInit ();
	
	atexit (quit);

	display = new Display (width, height, bpp, flags);

	dirty.init (0, 0, width, height);

	ResourceManager manager ("resources.rs");
	
	// load all resources
	manager.loadAll ();
	
	fonts.avant18 = getFont (manager.get ("fonts/avantgarde18"));
	fonts.avant18->setTargetSurface (display);

	cursors.arrow1 = getCursor (manager.get ("cursors/arrow1"));
	
	images.logo1 = getSurface (manager.get ("images/logo1"));
	
	setCursor (cursors.arrow1);

	Mouse::showCursor (0);

	if ((flags & SDL::FULLSCREEN) == SDL::FULLSCREEN)
		SDL::Delay (1000);

	if (doWelcome () == 0)
		goto end;
	
end:
	releaseCursor ();
		
    } 
    catch (Error & error) 
    {
	std::cout << "Exception caught: " << error.str () << std::endl;
    }
	
	return 0;	
};


int 
main (int argc, char **argv)
{
	ResourceTypeRegister <ResourceCursor> cursor_register ("cursor");
	srand (1024);
	App app;	
	return app.main (argc, argv);
};
